﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Monitoring;
using ScrumTable.Common.Security;
using ScrumTable.Common.Threading;
using ScrumTable.DL.Data;
using ScrumTable.DL.Data.Common;
using ScrumTable.DL.Data.Local;
using ScrumTable.DL.Data.Local.Test;

#endregion

namespace ScrumTable.BL.DM.Test.DataManagement
{
    /// <summary>
    /// This is the test class for DomainContext class.
    /// </summary>
    [TestClass]
    public class DomainContextTest
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private IDomainContext _context;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        #endregion

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new DomainContextTest instance.
        /// </summary>
        public DomainContextTest()
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Use TestInitialize to run code before running each test .
        /// </summary>
        [TestInitialize]
        public void TestInitialize()
        {
            IDataContext data = DataConnectionFactory.Create(DataAssembly.FromAssembly(typeof(DataContext).Assembly));
            _context = DomainContextFactory.Create(data);
            _context.Connect(
                new SyncDataDispatcher(),
                new ConnectionInformationManager(ConnectionInformation.Empty),
                delegate { },
                Resources.GetProjectTestDataFilePath(TestContext.TestDir));

            _context.Projects[Resources.Project1Id].LoadData(new NullDataImportMonitor());
            _context.Projects[Resources.Project2Id].LoadData(new NullDataImportMonitor());
        }

        /// <summary>
        /// Use TestCleanup to run code after each test has run.
        /// </summary>
        [TestCleanup]
        public void TestCleanup()
        {
            _context.Dispose();
            _context = null;
        }

        [TestMethod]
        public void TestProjects()
        {
            Assert.IsNotNull(_context.Projects);
            Assert.AreEqual(Resources.ProjectCount, _context.Projects.Count);
        }

        [TestMethod]
        public void TestDriverInfo()
        {
            Assert.IsNotNull(_context.DriverInfo);
            Assert.IsNotNull(_context.DriverInfo.Name);
            Assert.IsNotNull(_context.DriverInfo.Driver);
            Assert.IsNotNull(_context.DriverInfo.Version);
        }

        [TestMethod]
        public void TestDisableCollectionChangedEvents()
        {
            const int projectAddCount = 3;
            bool eventOccurred = false;
            bool multipleEventOccurred = false;

            DomainCollectionChangedEventHandler collectionChanged =
                delegate(object sender, DomainCollectionChangedEventArgs e)
                    {
                        eventOccurred = true;
                        multipleEventOccurred = (e is MultipleDomainCollectionChangedEventArgs
                            && ((MultipleDomainCollectionChangedEventArgs)e).Children.Count() == projectAddCount
                            && e.Action == DomainCollectionChangedAction.None
                            && e.ChangedItem == null
                            && e.Owner == null);
                    };

            _context.CollectionChanged += collectionChanged;
            try
            {
                using (_context.DisableCollectionChangedEvents())
                {
                    for (int i = 0; i < projectAddCount; ++i)
                    {
                        _context.Projects.First().Stories.AddNew();
                    }
                    Assert.IsFalse(eventOccurred);
                }
                Assert.IsTrue(eventOccurred);
                Assert.IsTrue(multipleEventOccurred);
            }
            finally
            {
                _context.CollectionChanged -= collectionChanged;
            }
        }

        [TestMethod]
        public void TestDisableElementChangedEvents()
        {
            const string changePropName = "Name";
            const int changedCount = 3;
            bool eventOccurred = false;
            bool multipleEventOccurred = false;

            PropertyChangedEventHandler pptyChanged =
                delegate(object sender, PropertyChangedEventArgs e)
                {
                    eventOccurred = true;
                    multipleEventOccurred = (e is MultipleDomainChangedEventArgs
                        && ((MultipleDomainChangedEventArgs)e).Children.Where(args => args.PropertyName == changePropName).Count() == changedCount
                        && ((MultipleDomainChangedEventArgs)e).ChangedElement == null
                        && e.PropertyName == string.Empty);
                };

            _context.ElementChanged += pptyChanged;
            try
            {
                using (_context.DisableElementChangedEvents())
                {
                    for (int i = 0; i < changedCount; ++i)
                    {
                        _context.Projects[Resources.Project1Id].Iterations[Resources.Iteration1Id].Stories[Resources.UserStory3Id].Name += i;
                    }
                    Assert.IsFalse(eventOccurred);
                }
                Assert.IsTrue(eventOccurred);
                Assert.IsTrue(multipleEventOccurred);
            }
            finally 
            {
                _context.ElementChanged -= pptyChanged;
            }
        }

        [TestMethod]
        public void TestDisableElementChangingEvents()
        {
            const string changePropName = "Name";
            const int changedCount = 3;
            bool eventOccurred = false;
            bool multipleEventOccurred = false;

            PropertyChangingEventHandler pptyChanging =
                delegate(object sender, PropertyChangingEventArgs e)
                {
                    eventOccurred = true;
                    multipleEventOccurred = (e is MultipleDomainChangingEventArgs
                        && ((MultipleDomainChangingEventArgs)e).Children.Where(args => args.PropertyName == changePropName).Count() == changedCount
                        && ((MultipleDomainChangingEventArgs)e).ChangedElement == null
                        && e.PropertyName == string.Empty);
                };

            _context.ElementChanging += pptyChanging;
            try
            {
                using (_context.DisableElementChangingEvents())
                {
                    for (int i = 0; i < changedCount; ++i)
                    {
                        _context.Projects[Resources.Project1Id].Iterations[Resources.Iteration1Id].Stories[Resources.UserStory3Id].Name += i;
                    }
                    Assert.IsFalse(eventOccurred);
                }
                Assert.IsTrue(eventOccurred);
                Assert.IsTrue(multipleEventOccurred);
            }
            finally
            {
                _context.ElementChanging -= pptyChanging;
            }
        }

        [TestMethod]
        public void TestForceChangeUser()
        {
            TestChangeUser((context, toChange) => context.ChangeUser(toChange));
        }

        [TestMethod]
        public void TestChangeUser()
        {
            bool changeEventFired = false;
            TestChangeUser((context, toChange) => context.ChangeUser(toChange, callback => changeEventFired = (callback is ICancellableDataResult)));
            Assert.IsTrue(changeEventFired);
        }

        private void TestChangeUser(Action<IDomainContext, Member> changeUserAction)
        {
            // remeber current login member
            IList<ConnectionInformation> connectionInfo = _context.Projects[Resources.Project1Id].Members.Convert(
                mem =>
                {
                    var conInfo = new ConnectionInformation(mem.Name, string.Empty);
                    conInfo.BackEndId = mem.Name;
                    return conInfo;
                });

            // create new context and login with the current member
            IDataContext data = DataConnectionFactory.Create(DataAssembly.FromAssembly(typeof(DataContext).Assembly));
            IDomainContext context = DomainContextFactory.Create(data);
            context.Connect(
                new SyncDataDispatcher(),
                new ConnectionInformationManager(connectionInfo.ToArray()),
                delegate { },
                Resources.GetProjectTestDataFilePath(TestContext.TestDir));

            context.Projects[Resources.Project1Id].LoadData(new NullDataImportMonitor());

            // make sure the current project is loaded with the current member credentials
            Assert.AreEqual(context.Projects[Resources.Project1Id].Members.First().BackEndId, context.ConnectionInformation.BackEndId);
            Assert.IsTrue(context.Projects[Resources.Project1Id].Members.First().CanLogIn);
            Assert.IsTrue(context.Projects[Resources.Project1Id].Members.Second().CanLogIn);

            // change user (to second instance)
            changeUserAction(context, context.Projects[Resources.Project1Id].Members.Second());
            Assert.AreEqual(context.Projects[Resources.Project1Id].Members.Second().BackEndId, context.ConnectionInformation.BackEndId);
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
